home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C++
/
Applications
/
PICSee Dust 1.01
/
Quaternary Source
/
SmartDragWindow.c
< prev
next >
Wrap
Text File
|
1995-11-22
|
24KB
|
779 lines
/*
SmartDragWindow.c
Written by Hiep Dam
From The Witches' Brew
Date: November 16, 1995
Internet: starlabs@aol.com
---------------------------------------------------------------------
This is in the public domain. Insert it into all of your applications!
(There's no reason not to!) <grin>
This routine does the right thing for normal & tricky situations:
* ...handling multiple monitor situations
(thanks to GetDominantDevice())
* ...not drawing over other windows if they are on top
of the window that is dragged (a subtle fact missed until
I noticed how the Finder implemented command-dragging).
* ...hiding the drag outline if the mouse moves outside of
the limit rect - just like DragWindow.
Note: Because this routine fiddles with the Window Manager port,
compatibility with future versions of the system software may be
compromised. But this looks to be waaaaay in the future...
The code is "somewhat" Copland-aware (uses the STRICT_WINDOWS accessors).
Feel free to make changes/improvements to the code and distribute
it as you wish. Just drop me a copy, OK?
---------------------------------------------------------------------
Portions written by Norman Basham, and a few ideas here and there
taken from C.K. Haun.
Version History:
1.0 11/16/95 Wrote it. Initially much easier than I thought.
Then slightly harder when trying to handle the
tricky situations above. But still not difficult
at all...
1.1 11/18/95 Added support for snap procs, and thus support
for grid snapping and window snapping as well.
1.0.1 11/18/95 Noticed slight discrepancy when dragging near
menubar. Added code to exclude menubar region
as if it was an overlapping window.
*/
#include "SmartDragWindow.h"
#ifndef __LOWMEM__
#include <LowMem.h>
#endif
// ---------------------------------------------------------------------------
static Boolean DoWindowDrag(
WindowPtr dragWindow,
Point startPoint,
const Rect *limitRect,
short snapToDistance,
Rect *dragResultRect,
SnapCallback snapProc);
static void AdjustWindowForStructure(WindowRef theWindow, Rect *adjustRect);
static void SnapBottomToBottom(Rect *snapRect, const Rect *magnetRect, short snapToDistance);
static void SnapTopToTop(Rect *snapRect, const Rect *magnetRect, short snapToDistance);
static void SnapRightToRight(Rect *snapRect, const Rect *magnetRect, short snapToDistance);
static void SnapLeftToLeft(Rect *snapRect, const Rect *magnetRect, short snapToDistance);
static Boolean WithinRange(short theValue, short rangeValue, short range);
static void MoveRectTo(Rect *theRect, short h, short v);
static GDHandle GetDominantDevice (Rect *r);
static Boolean IsActiveScreenDevice();
static long GetRectArea(Rect r);
// ---------------------------------------------------------------------------
#pragma mark === Hiep Dam ===
enum {
kSnapToMargin = 10
};
#define kExtremeNeg -32768
#define kExtremePos (32767 - 1) // required to address an old region bug,
//see develop 20 Q&As
#define HOTKEY_DISABLE IsControlKeyDown()
// ---------------------------------------------------------------------------
void SmartDragWindow(
WindowRef windowToDrag,
Point startPoint,
const Rect *limitRect,
short snapToDistance) {
SuperSmartDragWindow(windowToDrag, startPoint, limitRect, snapToDistance, MonitorSnapProc);
} // END SmartDragWindow
// ---------------------------------------------------------------------------
void SuperSmartDragWindow(
WindowRef windowToDrag,
Point startPoint,
const Rect *limitRect,
short snapToDistance,
SnapCallback snapProc) {
GrafPtr wMgrPort;
RgnHandle wMgrSaveRgn;
RgnHandle clipRgn;
RgnHandle strucRgn;
PenState wMgrPenState;
Rect dragResult;
Rect menuBarRect;
Boolean selectWindow;
Boolean dragOK;
WindowPtr aWindow;
GrafPtr savePort;
Rect wideOpen;
if (HOTKEY_DISABLE)
snapProc = NULL;
// Is this a normal drag or a "cmd-key" drag?
selectWindow = true;
if (windowToDrag != FrontWindow() && IsCmdKeyDown())
selectWindow = false;
GetPort(&savePort);
GetWMgrPort(&wMgrPort);
// Set to window manager port & get its settings (pen, clipping region)
SetPort(wMgrPort);
GetPenState(&wMgrPenState);
wMgrSaveRgn = NewRgn();
GetClip(wMgrSaveRgn);
// Enlarge clipping area so we can drag all over the place...
SetRect(&wideOpen, kExtremeNeg, kExtremeNeg, kExtremePos, kExtremePos);
ClipRect(&wideOpen);
clipRgn = NewRgn();
GetClip(clipRgn);
strucRgn = NewRgn();
// Account for any windows which may be on top of ours. If there
// are any, subtract their structure regions from the clipping
// region so we don't draw into them (just like what DragWindow() does).
if (windowToDrag != FrontWindow()) {
// Get first window
aWindow = FrontWindow();
while (aWindow != windowToDrag && aWindow != NULL) {
if (IsWindowVisible(aWindow)) {
GetWindowStructureRgn(aWindow, strucRgn);
DiffRgn(clipRgn, strucRgn, clipRgn);
}
aWindow = GetNextWindow(aWindow);
}
}
// Remember to exclude menu bar as well. In this case we treat
// it like one of the windows above - we subtract it's area
// from the total draggable clipping region.
// This part unfortunately relies on a low-memory global...
menuBarRect = (**GetMainDevice()).gdRect;
menuBarRect.bottom = menuBarRect.top + LMGetMBarHeight();
RectRgn(strucRgn, &menuBarRect);
DiffRgn(clipRgn, strucRgn, clipRgn);
DisposeRgn(strucRgn);
// Set our "adjusted" clipping region
SetClip(clipRgn);
// Illegal snap-to distance?
if (snapToDistance < 0)
snapToDistance = kSnapToMargin;
// Do the actual dirty work
dragOK = DoWindowDrag(
windowToDrag,
startPoint,
limitRect,
snapToDistance,
&dragResult,
snapProc);
// Restore window manager port
SetClip(wMgrSaveRgn);
SetPenState(&wMgrPenState);
DisposeRgn(wMgrSaveRgn);
DisposeRgn(clipRgn);
if (dragOK) {
// Adjust dragResult for structure region of window (i.e. drag bar)
AdjustWindowForStructure(windowToDrag, &dragResult);
MoveWindow(windowToDrag, dragResult.left, dragResult.top, selectWindow);
}
SetPort(savePort);
} // END SmartDragWindow
// ---------------------------------------------------------------------------
/*
DoWindowDrag().
The real meat 'n potatoes of the outfit.
Returns true if mouseloc was inside limitRect when mouse was released,
else false.
If true, argument <dragResultRect> will contain the new dragged rect
(note: the rect is the size of the window's structure region and
needs to be adjusted before calling MoveWindow())
*/
Boolean DoWindowDrag(
WindowPtr dragWindow,
Point startPoint,
const Rect *limitRect,
short snapToDistance,
Rect *dragResultRect,
SnapCallback snapProc) {
Rect dragRect;
Rect oldRect;
Rect confineRect;
Rect grayRgnRect;
RgnHandle strucRgn;
Point mouseLoc;
short offsetH,
offsetV;
short windowWidth,
windowHeight;
Boolean insideConfineRect;
char i;
Pattern grayPat;
Pattern blackPat;
/*
Why can't we auto-initialize the patterns grayPat & blackPat,
as in "grayPat = { 0xAA, 0x55, 0xAA, 0x55, etc... }"?
Well, looking at the definition of a Pattern, it's a string
of 8 chars! So the above is a string initialization, which in
turn makes the initialization string 0xAA, 0x55 a global
string. This is NOT what is intended! Doing this will cause
the compiler to allocate at least 16 bytes of global data.
*/
for (i = 0; i < 8; i++) {
grayPat.pat[i] = 0xAA;
blackPat.pat[i] = 0xAA;
}
for (i = 1; i < 8; i += 2)
grayPat.pat[i] = 0x55;
// Determine limit rect (& be smart about it!)
grayRgnRect = (**GetGrayRgn()).rgnBBox;
if (limitRect == NULL || EmptyRect(limitRect)) {
// Don't use screenBits.bounds, please!
confineRect = grayRgnRect;
InsetRect(&confineRect, 4, 4);
}
else {
// If users passed the grayRgn or screenBits.bounds, adjust
// the limit rect for them, just like DragWindow() does.
// We shouldn't use globals, so assuming that screenBits.bounds
// is the same as (**GetMainDevice).gdRect...
if (EqualRect(limitRect, &grayRgnRect) ||
EqualRect(limitRect, &(**GetMainDevice()).gdRect)) {
confineRect = grayRgnRect;
InsetRect(&confineRect, 4, 4);
}
else {
// Passed a non-standard limit rect. Use it instead.
confineRect = *limitRect;
}
}
insideConfineRect = true;
// Get window height, width
strucRgn = NewRgn();
GetWindowStructureRgn(dragWindow, strucRgn);
windowWidth = dragRect.right - dragRect.left;
windowHeight = dragRect.bottom - dragRect.top;
dragRect = (**strucRgn).rgnBBox;
// Since the mouse probably isn't at the topleft of the window,
// we have to make adjustments
offsetH = startPoint.h - dragRect.left;
offsetV = startPoint.v - dragRect.top;
// Now we can rubberband
PenMode(srcXor);
PenPat(&grayPat);
// Draw it for the first time
oldRect = dragRect;
FrameRect(&dragRect);
while (StillDown()) {
GetMouse(&mouseLoc);
if (PtInRect(mouseLoc, &confineRect)) {
MoveRectTo(&dragRect, mouseLoc.h - offsetH, mouseLoc.v - offsetV);
if (snapProc)
snapProc(dragWindow, snapToDistance, &dragRect);
// Draw only if mouse moved and it moved inside of limit rect
if (!insideConfineRect) {
// Mouse was outside of limit rect and now it
// has moved back inside.
FrameRect(&dragRect);
oldRect = dragRect;
}
else if (!EqualRect(&oldRect, &dragRect)) {
FrameRect(&dragRect);
FrameRect(&oldRect);
oldRect = dragRect;
}
insideConfineRect = true;
}
else {
if (insideConfineRect) {
// Mouse was inside limit rect and now has moved
// outside of it.
// Erase drag outline
FrameRect(&oldRect);
insideConfineRect = false;
}
}
}
// Erase the last drag outline (if it hasn't been
// already erased by the mouse being outside of confineRect)
if (insideConfineRect)
FrameRect(&dragRect);
DisposeRgn(strucRgn);
// Restore drawing modes
PenMode(srcCopy);
PenPat(&blackPat);
if (insideConfineRect)
*dragResultRect = dragRect;
return(insideConfineRect);
} // END DoWindowDrag
// ---------------------------------------------------------------------------
/*
Adjust the drag rect by accounting for the window's drag bar.
DoWindowDrag() returns the dragged rect - but this rect is the
total structure rect of the window. MoveWindow(), on the other
hand, expects the rect to be in terms of the content region.
So we have to adjust it...
*/
void AdjustWindowForStructure(WindowRef theWindow, Rect *adjustRect) {
RgnHandle strucRgn, contRgn;
strucRgn = NewRgn();
contRgn = NewRgn();
GetWindowStructureRgn(theWindow, strucRgn);
GetWindowContentRgn(theWindow, contRgn);
// We only have to worry about the left and top sides
// of the rect, since MoveWindow takes only h and v arguments.
adjustRect->top += (**contRgn).rgnBBox.top - (**strucRgn).rgnBBox.top;
adjustRect->left += (**contRgn).rgnBBox.left - (**strucRgn).rgnBBox.left;
// Here is a tricky situation. If the window is "rolled up", a la
// Aaron, we get errorneous contRgn areas. The top
// of the contRgn is one less than it is if it wasn't rolled up.
// So we have to adjust for this.
// WindowShade does use the correct values.
/*
if ((**contRgn).rgnBBox.top == (**contRgn).rgnBBox.bottom) {
adjustRect->top++;
}
*/
DisposeRgn(strucRgn);
DisposeRgn(contRgn);
} // END AdjustWindowForStructure
// ---------------------------------------------------------------------------
/*
MonitorSnapProc.
Snaps the rect to the edges of the monitor. Multiple-monitor savvy.
*/
void MonitorSnapProc(WindowPtr windowToDrag, short snapToDistance, Rect *snapRect) {
GDHandle theMonitor;
Rect monitorRect;
// This is where we adjust the drag rect to "snap to" the
// edges of the monitors.
theMonitor = GetDominantDevice(snapRect);
monitorRect = (**theMonitor).gdRect;
if (theMonitor == GetMainDevice()) {
// If this is the main monitor, adjust "snap to" rect
// to account for menu bar.
monitorRect.top += LMGetMBarHeight();
}
// Is the rect close enought to left or right edges
// of the magnetRect?
SnapRightToRight(snapRect, &monitorRect, snapToDistance);
SnapLeftToLeft(snapRect, &monitorRect, snapToDistance);
// How about the top or bottom edges?
SnapBottomToBottom(snapRect, &monitorRect, snapToDistance);
SnapTopToTop(snapRect, &monitorRect, snapToDistance);
} // END MonitorSnapProc
// ---------------------------------------------------------------------------
/*
WindowSnapProc.
Snap to edges of other windows.
*/
enum {
kMaxWindowsSnap = 10
};
void WindowSnapProc(WindowPtr windowToDrag, short snapToDistance, Rect *snapRect) {
Rect strucRgnRect;
short snapRectWidth, snapRectHeight;
short i, numWindows;
WindowRef theWindow;
WindowRef windowList[kMaxWindowsSnap];
RgnHandle strucRgn;
theWindow = FrontWindow();
if (theWindow == NULL) return;
snapRectWidth = snapRect->right - snapRect->left;
snapRectHeight = snapRect->bottom - snapRect->top;
// Make a list of windows which meet our criteria
// of "snappable" windows
i = numWindows = 0;
while ((theWindow != NULL) && (i < kMaxWindowsSnap)) {
if (IsWindowVisible(theWindow) && (theWindow != windowToDrag)) {
windowList[i++] = theWindow;
numWindows++;
}
theWindow = GetNextWindow(theWindow);
}
strucRgn = NewRgn();
// We're going to give the topmost window the highest priority
// when determining which window to snap to. To do this, we
// step through the window list backwards (since the topmost
// window is the first entry in the window list).
for (i = numWindows-1; i >= 0; i--) {
GetWindowStructureRgn(windowList[i], strucRgn);
strucRgnRect = (**strucRgn).rgnBBox;
/*
Below is a tortuous road of if-then-checks. sigh.
The problem here is that we have to do eight checks:
the left of the snap window to the left of the magnet window,
the left of the snap window to the right of the magnet window,
the top of the snap window to the top of the magnet window,
the top of the snap window to the bottom of the magnet window,
and so on for all 4 sides x 2...
*/
// Check left to left snapping
if (WithinRange(snapRect->left, strucRgnRect.left, snapToDistance)) {
if ((snapRect->top > strucRgnRect.bottom) ||
(snapRect->bottom < strucRgnRect.top)) {
// Do nothing
}
else {
snapRect->left = strucRgnRect.left;
snapRect->right = snapRect->left + snapRectWidth;
}
// Top gets preference, so we check that last
SnapBottomToBottom(snapRect, &strucRgnRect, snapToDistance);
SnapTopToTop(snapRect, &strucRgnRect, snapToDistance);
}
// Check right to right snapping
else if (WithinRange(snapRect->right, strucRgnRect.right, snapToDistance)) {
if ((snapRect->top > strucRgnRect.bottom) ||
(snapRect->bottom < strucRgnRect.top)) {
// Do nothing
}
else {
snapRect->right = strucRgnRect.right;
snapRect->left = snapRect->right - snapRectWidth;
}
// Top gets preference, so we check that last
SnapBottomToBottom(snapRect, &strucRgnRect, snapToDistance);
SnapTopToTop(snapRect, &strucRgnRect, snapToDistance);
}
// Check top to top snapping
if (WithinRange(snapRect->top, strucRgnRect.top, snapToDistance)) {
if ((snapRect->left > strucRgnRect.right) ||
(snapRect->right < strucRgnRect.left)) {
// Do nothing
}
else {
snapRect->top = strucRgnRect.top;
snapRect->bottom = snapRect->top + snapRectHeight;
}
// Left get preference, so check that last
SnapRightToRight(snapRect, &strucRgnRect, snapToDistance);
SnapLeftToLeft(snapRect, &strucRgnRect, snapToDistance);
}
// Check bottom to bottom snapping
else if (WithinRange(snapRect->bottom, strucRgnRect.bottom, snapToDistance)) {
if ((snapRect->left > strucRgnRect.right) ||
(snapRect->right < strucRgnRect.left)) {
// Do nothing
}
else {
snapRect->bottom = strucRgnRect.bottom;
snapRect->top = snapRect->bottom - snapRectHeight;
}
// Left get preference, so check that last
SnapRightToRight(snapRect, &strucRgnRect, snapToDistance);
SnapLeftToLeft(snapRect, &strucRgnRect, snapToDistance);
}
// Check left to right snapping
if (WithinRange(snapRect->left, strucRgnRect.right, snapToDistance)) {
if ((snapRect->top > strucRgnRect.bottom) ||
(snapRect->bottom < strucRgnRect.top)) {
// Do nothing
}
else {
snapRect->left = strucRgnRect.right;
snapRect->right = snapRect->left + snapRectWidth;
}
// Top gets preference, so we check that last
SnapBottomToBottom(snapRect, &strucRgnRect, snapToDistance);
SnapTopToTop(snapRect, &strucRgnRect, snapToDistance);
}
// Check right to left snapping
else if (WithinRange(snapRect->right, strucRgnRect.left, snapToDistance)) {
if ((snapRect->top > strucRgnRect.bottom) ||
(snapRect->bottom < strucRgnRect.top)) {
// Do nothing
}
else {
snapRect->right = strucRgnRect.left;
snapRect->left = snapRect->right - snapRectWidth;
}
// Top gets preference, so we check that last
SnapBottomToBottom(snapRect, &strucRgnRect, snapToDistance);
SnapTopToTop(snapRect, &strucRgnRect, snapToDistance);
}
// Check top to bottom snapping
if (WithinRange(snapRect->top, strucRgnRect.bottom, snapToDistance)) {
if ((snapRect->left > strucRgnRect.right) ||
(snapRect->right < strucRgnRect.left)) {
// Do nothing
}
else {
snapRect->top = strucRgnRect.bottom;
snapRect->bottom = snapRect->top + snapRectHeight;
}
// Left get preference, so check that last
SnapRightToRight(snapRect, &strucRgnRect, snapToDistance);
SnapLeftToLeft(snapRect, &strucRgnRect, snapToDistance);
}
// Check bottom to top snapping
else if (WithinRange(snapRect->bottom, strucRgnRect.top, snapToDistance)) {
if ((snapRect->left > strucRgnRect.right) ||
(snapRect->right < strucRgnRect.left)) {
// Do nothing
}
else {
snapRect->bottom = strucRgnRect.top;
snapRect->top = snapRect->bottom - snapRectHeight;
}
// Left get preference, so check that last
SnapRightToRight(snapRect, &strucRgnRect, snapToDistance);
SnapLeftToLeft(snapRect, &strucRgnRect, snapToDistance);
}
}
DisposeRgn(strucRgn);
} // END WindowSnapProc
// ---------------------------------------------------------------------------
/*
GridSnapProc.
Snap rect to the grid.
*/
void GridSnapProc(WindowPtr windowToDrag, short gridSize, Rect *snapRect) {
short leftMod, topMod;
short snapRectWidth, snapRectHeight;
snapRectWidth = snapRect->right - snapRect->left;
snapRectHeight = snapRect->bottom - snapRect->top;
leftMod = snapRect->left / gridSize;
snapRect->left = leftMod * gridSize;
topMod = snapRect->top / gridSize;
snapRect->top = topMod * gridSize;
snapRect->right = snapRect->left + snapRectWidth;
snapRect->bottom = snapRect->top + snapRectHeight;
} // END GridSnapProc
// ---------------------------------------------------------------------------
void SnapBottomToBottom(Rect *snapRect, const Rect *magnetRect, short snapToDistance) {
short snapRectHeight;
if (WithinRange(snapRect->bottom, magnetRect->bottom, snapToDistance)) {
snapRectHeight = snapRect->bottom - snapRect->top;
snapRect->bottom = magnetRect->bottom;
snapRect->top = snapRect->bottom - snapRectHeight;
}
} // END SnapBottomToBottom
void SnapTopToTop(Rect *snapRect, const Rect *magnetRect, short snapToDistance) {
short snapRectHeight;
if (WithinRange(snapRect->top, magnetRect->top, snapToDistance)) {
snapRectHeight = snapRect->bottom - snapRect->top;
snapRect->top = magnetRect->top;
snapRect->bottom = snapRect->top + snapRectHeight;
}
} // END SnapTopToTop
void SnapRightToRight(Rect *snapRect, const Rect *magnetRect, short snapToDistance) {
short snapRectWidth;
if (WithinRange(snapRect->right, magnetRect->right, snapToDistance)) {
snapRectWidth = snapRect->right - snapRect->left;
snapRect->right = magnetRect->right;
snapRect->left = snapRect->right - snapRectWidth;
}
} // END SnapRightToRight
void SnapLeftToLeft(Rect *snapRect, const Rect *magnetRect, short snapToDistance) {
short snapRectWidth;
if (WithinRange(snapRect->left, magnetRect->left, snapToDistance)) {
snapRectWidth = snapRect->right - snapRect->left;
snapRect->left = magnetRect->left;
snapRect->right = snapRect->left + snapRectWidth;
}
} // END SnapLeftToLeft
// ---------------------------------------------------------------------------
Boolean WithinRange(short theValue, short rangeValue, short range) {
if (theValue < rangeValue + range &&
theValue > rangeValue - range)
return(true);
else
return(false);
} // END WithinRange
// ---------------------------------------------------------------------------
void MoveRectTo(Rect *theRect, short h, short v) {
short width = theRect->right - theRect->left;
short height = theRect->bottom - theRect->top;
theRect->left = h;
theRect->top = v;
theRect->right = theRect->left + width;
theRect->bottom = theRect->top + height;
} // END MoveRectTo
// ---------------------------------------------------------------------------
/*
Quick 'n dirty. Mea culpa...
*/
Boolean IsKeyDown(unsigned short theKey);
Boolean IsKeyDown(unsigned short theKey) {
unsigned char km[16];
GetKeys(*((KeyMap*) &km));
return ((km[theKey>>3] >> (theKey & 7)) & 1);
}
Boolean IsCmdKeyDown() {
return IsKeyDown(0x37);
}
Boolean IsControlKeyDown() {
return IsKeyDown(0x3B);
}
// ---------------------------------------------------------------------------
/*
The following routines were written by Norman Basham.
They were taken from "Monitors.cpp"
Thanks a mil, Norman!
*/
#pragma mark === Norman Basham ===
// ------------------------------------------------------------------------
// Given rect r, which device does it overlap most. An example of its use
// would be passing in (**wp->visRgn).rgnBBox to find out which device a
// window is overlapping the most, as in the case of zooming a window.
// ------------------------------------------------------------------------
GDHandle GetDominantDevice (Rect *r)
{
GDHandle aGDevice;
GDHandle bigGDevice;
Rect screenRect;
Rect sectRect;
long area;
long biggestArea = 0L;
aGDevice = GetDeviceList (); // start at begining of device list
while (aGDevice != nil) // loop if device exists
{
if (IsActiveScreenDevice (aGDevice)) // if device is a monitor and active
{
screenRect = (**aGDevice).gdRect; // get the devices global rect
SectRect (&screenRect, r, §Rect); // get overlapping rect of device and r
area = GetRectArea (sectRect); // changed 3/21/94
if (area > biggestArea) // if overlapping rect has the biggest area
{
bigGDevice = aGDevice; // set big device to current device
biggestArea = area; // set big area to current area
}
aGDevice = GetNextDevice (aGDevice); // check next device in list
}
}
return bigGDevice; // return device containing biggest portion of r
}
// ------------------------------------------------------------------------
// Given a device, return wether it is a monitor and wether it's active
// ------------------------------------------------------------------------
Boolean IsActiveScreenDevice(GDHandle theDevice)
{
return (
(TestDeviceAttribute (theDevice, screenDevice)) &&
(TestDeviceAttribute (theDevice, screenActive))
);
}
long GetRectArea(Rect r)
{
Rect temp = r;
OffsetRect (&temp, -temp.left, -temp.top); // rids us of neg values
return (long) temp.right * temp.bottom; // return width x heigth
}